package zdream.control.assets;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 原名: TextureSheet
 *
 * @author Zdream
 * @date 2024-01-08
 * @since 0.0.1
 */
public class TextureDescription {
	public int blockWidth, blockHeight;

	public Sequence defaultSequence;
	public Sequence[] sequences;
	public Sheet[] sheets;

	public static class Sequence {
		public final TextureDescription parent;

		public Sequence(TextureDescription parent) {
			this.parent = Objects.requireNonNull(parent);
		}

		public String state;
		public Region[] regions;
		public float stepInSeconds;
		public int loopIndex;
	}

	public static class Sheet {
		public final TextureDescription parent;

		public Sheet(TextureDescription parent) {
			this.parent = Objects.requireNonNull(parent);
		}

		/**
		 * 读取的时候会把它转换成基于整个 application 的相对路径
		 */
		public String imagePath;
		public Region[] regions;
	}

	public static class Region {
		public final Sheet parent;

		public Region(Sheet parent) {
			this.parent = Objects.requireNonNull(parent);
		}

		public String name;
		public int x, y, width, height;
		public int offsetX, offsetY;
		public boolean flipX, flipY;
	}

	/* **********
	 * 实用方法 *
	 ********** */

	public boolean hasState(String stateName) {
		return Arrays.stream(sequences)
				.anyMatch(sequence -> sequence.state.equals(stateName));
	}

	public Sequence findState(String stateName) {
		return Arrays.stream(sequences)
				.filter(sequence -> sequence.state.equals(stateName))
				.findAny()
				.orElse(null);
	}

	public Region findRegion(String regionName) {
		return Arrays.stream(sheets)
				.flatMap(sheet -> Arrays.stream(sheet.regions))
				.filter(region -> region.name.equals(regionName))
				.findAny()
				.orElse(null);
	}

	/* **********
	 * 构建方法 *
	 ********** */

	public static TextureDescription parse(String text, Path relatedPath) throws JsonSyntaxException {
		final JsonObject root = JsonParser.parseString(text).getAsJsonObject();
		return parse(root, relatedPath);
	}

	public static TextureDescription parse(JsonObject root, Path relatedPath) {
		TextureDescription description = new TextureDescription();

		if (root.has("blockWidth")) {
			description.blockWidth = root.get("blockWidth").getAsInt();
			description.blockHeight = root.get("blockHeight").getAsInt();
		} else {
			description.blockWidth = 16;
			description.blockHeight = 16;
		}

		parseTextures(description, root, relatedPath);
		parseSequences(description, root);
		return description;
	}

	private static void parseTextures(TextureDescription description, JsonObject root, Path relatedPath) {
		JsonArray texturesArray = root.getAsJsonArray("textures");
		if (texturesArray.isEmpty()) {
			description.sheets = new Sheet[0];
			return;
		}

		final JsonElement imageElement = root.get("image");
		if (imageElement != null) {
			// has only one image
			Path imagePath = relatedPath.getParent().resolve(imageElement.getAsString()).normalize();
			final Sheet sheet = parseOneTextureArray(description, texturesArray, imagePath.toString());
			description.sheets = new Sheet[] { sheet };
			return;
		}

		// 当有多个 image 时, root.image 为 null, imagePath 将写到 root.textures[x].image 上
		// "textures":
		// [
		//   {
		//     "image": "a.png",
		//     "items": [
		//       {"name":"t1", "x":101 ...}, ...
		//     ]
		//   }
		// ]
		final ArrayList<Sheet> sheets = new ArrayList<>();
		for (JsonElement element : texturesArray) {
			JsonObject item = element.getAsJsonObject();
			Path imagePath = relatedPath.getParent().resolve(item.get("image").getAsString()).normalize();
			sheets.add(parseOneTextureArray(description, item.getAsJsonArray("items"), imagePath.toString()));
		}
		description.sheets = sheets.toArray(new Sheet[0]);
	}

	private static Sheet parseOneTextureArray(TextureDescription description, JsonArray array, String imagePath) {
		Sheet sheet = new Sheet(description);
		sheet.imagePath = imagePath;
		sheet.regions = array.asList().stream()
				.map(JsonElement::getAsJsonObject)
				.map(object -> parseRegion(object, sheet))
				.toArray(Region[]::new);
		return sheet;
	}

	private static Region parseRegion(JsonObject object, Sheet sheet) {
		Region region = new Region(sheet);
		region.name = object.get("name").getAsString();
		region.x = object.get("x").getAsInt();
		region.y = object.get("y").getAsInt();
		region.width = object.get("w").getAsInt();
		region.height = object.get("h").getAsInt();
		region.offsetX = object.get("offx").getAsInt();
		region.offsetY = object.get("offy").getAsInt();

		if (object.has("flipX")) {
			region.flipX = object.get("flipX").getAsBoolean();
		}
		if (object.has("flipY")) {
			region.flipY = object.get("flipY").getAsBoolean();
		}
		return region;
	}

	private static void parseSequences(TextureDescription description, JsonObject root) {
		final Map<String, Region> regionMap = Arrays.stream(description.sheets)
				.flatMap(sheet -> Arrays.stream(sheet.regions))
				.collect(Collectors.toMap(region -> region.name, Function.identity()));

		String defaultStateName = Optional.ofNullable(root.get("default"))
				.map(JsonElement::getAsString)
				.orElse(null);

		final JsonArray array = root.getAsJsonArray("select");
		if (array == null) {
			description.sequences = new Sequence[0];
			return;
		}

		List<Sequence> sequenceList = new ArrayList<>();
		for (JsonElement element : array) {
			JsonObject item = element.getAsJsonObject();
			Sequence sequence = new Sequence(description);

			sequence.state = item.get("state").getAsString();

			if (item.has("step")) {
				sequence.stepInSeconds = item.get("step").getAsFloat();
			} else {
				sequence.stepInSeconds = 86400;
			}

			if (item.has("loopIndex")) {
				sequence.loopIndex = item.get("loopIndex").getAsInt();
			} else {
				sequence.loopIndex = -1;
			}

			final JsonArray sequences = item.get("sequence").getAsJsonArray();
			Region[] regions = sequence.regions = new Region[sequences.size()];

			int index = 0;
			for (JsonElement sequenceItem : sequences) {
				String regionName = sequenceItem.getAsString();
				regions[index++] = Objects.requireNonNull(regionMap.get(regionName));
			}

			if (Objects.equals(defaultStateName, sequence.state)) {
				description.defaultSequence = sequence;
			}
			sequenceList.add(sequence);
		}
		description.sequences = sequenceList.toArray(new Sequence[0]);
		if (defaultStateName == null && description.sequences.length > 0) {
			description.defaultSequence = description.sequences[0];
		}
	}
}
